package com.springboot.frame.excel;

import com.springboot.frame.excel.annotation.ExcelAttribute;
import com.springboot.frame.excel.annotation.ExcelIgnore;
import com.springboot.frame.excel.annotation.ValidExcel;
import com.springboot.frame.excel.entity.ReaderParam;
import com.springboot.frame.excel.entity.ReaderSheet;
import com.springboot.frame.excel.enums.Excels;
import com.springboot.frame.excel.exception.ExcelImportException;
import com.springboot.frame.excel.handler.CellHandler;
import com.springboot.frame.excel.handler.CellTypeManager;
import com.springboot.frame.excel.util.ReflectionKit;
import com.springboot.frame.excel.util.ValidationKit;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.util.IOUtils;
import javax.validation.ValidationException;
import java.beans.IntrospectionException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;

/**
 * @author madman
 */
@Slf4j
public class ExcelReader {

  /**
   * Workbook对象
   */
  private Workbook workbook;

  /**
   * 文件薄(Sheet)索引
   */
  private int sheetIndex = 0;

  /**
   * 所有的ReaderSheet对象
   */
  private List<ReaderSheet> readerSheets = new LinkedList<>();

  private ExcelReader(Workbook workbook) {
    this.workbook = workbook;
  }

  /**
   * 通过Excel类型创建Workbook对象
   *
   * @param excels Excel类型
   * @param in 输入流
   * @return ExcelWriter对象
   */
  public static ExcelReader of(Excels excels, InputStream in) {
    try {
      Class<?> workBookClazz = Class.forName(excels.getName());
      Constructor<?> constructor = workBookClazz.getConstructor(InputStream.class);
      return new ExcelReader((Workbook) constructor.newInstance(in));
    } catch (ReflectiveOperationException e) {
      throw new ExcelImportException("创建文件薄失败", e);
    }
  }

  /**
   * 单个Sheet的ReaderSheet的创建
   *
   * @param clazz 导入对象类型
   * @return ExcelReader对象
   */
  public ExcelReader sheet(Class<?> clazz) {
    return this.sheet(clazz, 2);
  }

  /**
   * 单个Sheet的ReaderSheet的创建
   *
   * @param clazz clazz 导入对象类型
   * @param startRow 开始行
   * @return ExcelReader对象
   */
  public ExcelReader sheet(Class<?> clazz, int startRow) {
    Sheet sheet = workbook.getSheetAt(sheetIndex);
    List<ReaderParam> readerParams = initReaderParam(clazz);
    boolean valid = clazz.isAnnotationPresent(ValidExcel.class);
    readerSheets.add(new ReaderSheet(readerParams, sheet, clazz, Math.abs(startRow), valid));
    sheetIndex++;
    return this;
  }

  /**
   * 导入数据
   *
   * @return 多Sheet的数据
   */
  public List<List<?>> read() {
    List<List<?>> result = new ArrayList<>();
    for (ReaderSheet readerSheet : readerSheets) {
      List<?> perSheetResult = importFromSheet(readerSheet);
      result.add(perSheetResult);
    }
    IOUtils.closeQuietly(workbook);
    return result;
  }


  /**
   * 初始化所有的属性信息
   *
   * @param clazz 导入对象类型
   * @return 属性信息
   */
  private List<ReaderParam> initReaderParam(Class<?> clazz) {
    try {
      List<ReaderParam> readerParams = new ArrayList<>();
      for (Entry<Field, Method> entry : ReflectionKit.fieldAndSetterMethod(clazz).entrySet()) {
        Field field = entry.getKey();
        Method method = entry.getValue();
        if (!(field.isAnnotationPresent(ExcelIgnore.class) && field.getAnnotation(ExcelIgnore.class)
            .ignoreRead())) {
          field.setAccessible(true);
          CellHandler cellHandler = CellTypeManager.getHandler(field.getType());
          if (field.isAnnotationPresent(ExcelAttribute.class)) {
            Class<? extends CellHandler> handler = field.getAnnotation(ExcelAttribute.class)
                .typeHandler();
            if (!CellHandler.class.equals(handler)) {
              cellHandler = handler.newInstance();
            }
          }
          readerParams.add(new ReaderParam(method, cellHandler));
        }
      }
      return readerParams;
    } catch (IntrospectionException e) {
      throw new ExcelImportException("反射获取字段信息失败", e);
    } catch (ReflectiveOperationException e) {
      throw new ExcelImportException("反射创建typeHandler失败", e);
    }
  }

  /**
   * 从Sheet导入数据
   *
   * @param readerSheet ReaderSheet对象
   * @param <T> 导入数据对象类型
   * @return 数据
   */
  @SuppressWarnings("unchecked")
  private <T> List<T> importFromSheet(ReaderSheet readerSheet) {
    try {
      StringBuilder builder = new StringBuilder();
      Sheet sheet = readerSheet.getSheet();
      List<T> data = new ArrayList<>();
      // 获取所有的合并区域
      List<CellRangeAddress> rangeAddressList = sheet.getMergedRegions();
      // 跳过Title和Header,遍历行数据进行读取
      List<ReaderParam> readerParams = readerSheet.getReaderParams();
      boolean valid = readerSheet.isValid();
      for (int rowIndex = readerSheet.getStartRow(); rowIndex <= sheet.getLastRowNum();
          rowIndex++) {
        Row row = sheet.getRow(rowIndex);
        T object = (T) readerSheet.getClazz().newInstance();
        // 遍历列数据
        for (int columnIndex = 0; columnIndex < readerParams.size(); columnIndex++) {
          Cell cell = row.getCell(columnIndex);
          // 获取该单元格所在的合并区域，并判断该单元格是否有合并区域。
          CellRangeAddress rangeAddress = getMergedRegion(rangeAddressList, cell);
          if (rangeAddress != null) {
            int firstRow = rangeAddress.getFirstRow();
            int firstColumn = rangeAddress.getFirstColumn();
            Cell defaultCell = sheet.getRow(firstRow).getCell(firstColumn);
            // 依照默认的Cell设置数据
            setRangeObjectValue(object, defaultCell, readerParams.get(firstColumn),
                readerParams.get(columnIndex));
          } else {
            // 设置数据
            setObjectValue(object, cell, readerParams.get(columnIndex));
          }
        }
        data.add(object);
        // 验证数据
        if (valid) {
          String errorMsg = ValidationKit.validate(object);
          if (StringUtils.isNotEmpty(errorMsg)) {
            builder.append("第").append(rowIndex + 1).append("行").append(":").append(errorMsg);
          }
        }
      }
      // 判断是否有错误信息
      if (!builder.toString().isEmpty()) {
        throw new ValidationException(builder.toString());
      }
      return data;
    } catch (InstantiationException | IllegalAccessException e) {
      throw new ExcelImportException("数据对象创建失败", e);
    }
  }

  /**
   * 获取该单元格所在的合并区域
   *
   * @param rangeAddressList 所有的合并区域
   * @param cell 单元格
   * @return 该单元格所在的合并区域 or null
   */
  private CellRangeAddress getMergedRegion(List<CellRangeAddress> rangeAddressList, Cell cell) {
    if (cell != null) {
      int rowIndex = cell.getRowIndex();
      int columnIndex = cell.getColumnIndex();
      for (CellRangeAddress rangeAddress : rangeAddressList) {
        int firstRow = rangeAddress.getFirstRow();
        int lastRow = rangeAddress.getLastRow();
        int firstColumn = rangeAddress.getFirstColumn();
        int lastColumn = rangeAddress.getLastColumn();
        if ((rowIndex >= firstRow && rowIndex <= lastRow)) {
          if (columnIndex >= firstColumn && columnIndex <= lastColumn) {
            return rangeAddress;
          }
        }
      }
    }
    return null;
  }

  /**
   * 为合并区域对象设置数据
   *
   * @param object 数据对象
   * @param defaultCell 合并区域默认Cell
   * @param defaultParam 合并区域默认Cell参数
   * @param param 自己的cell参数
   */
  private void setRangeObjectValue(Object object, Cell defaultCell, ReaderParam defaultParam,
      ReaderParam param) {
    try {
      Object defaultValue = defaultParam.getHandler().read(defaultCell, object);
      ReflectionKit.setRefValue(object, param.getMethod(), defaultValue);
    } catch (ReflectiveOperationException e) {
      throw new ExcelImportException("设值失败", e);
    }
  }

  /**
   * 为对象设置数据
   *
   * @param object 数据对象
   * @param cell Cell对象
   * @param param 参数
   */
  private void setObjectValue(Object object, Cell cell, ReaderParam param) {
    try {
      Object value = param.getHandler().read(cell, object);
      ReflectionKit.setRefValue(object, param.getMethod(), value);
    } catch (ReflectiveOperationException e) {
      throw new ExcelImportException("设值失败", e);
    }
  }

}
